package lapsnake.model.dnd;

import java.awt.AWTEvent;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.AWTEventListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;
import java.beans.PropertyVetoException;

import lapsnake.model.LapNode;
import lapsnake.model.data.ChildField;
import lapsnake.ui.BlendComposite;

public class JigMover implements MouseListener, MouseMotionListener
{
	private enum DragState
	{
		NO(Color.BLACK), DRAGGING(new Color(0x80c9ff)), SNAP_INVALID(new Color(0xff6666)), SNAP_VALID(new Color(0x9eff7f));
		
		Color color;
		private DragState(Color c)
		{
			this.color = c;
		}
	}
	DragState state = DragState.NO;
	ActionNodePanel<?> dragging = null;
	int originalIndex;
	Point offset = new Point();
	Point start = new Point();
	Rectangle dragPosition = new Rectangle();
	
	StmtPanel rootPanel;
	/**True if current drag op will copy, false if it will move*/
	boolean copy;
	
	BufferedImage buffer;
	
	
	
	public JigMover(StmtPanel dndPanel)
	{
		this.rootPanel = dndPanel;
		rootPanel.addMouseListener(this);
		rootPanel.addMouseMotionListener(this);
	}
	
	@Override
	public void mouseClicked(MouseEvent e)
	{
		
	}
	
	@Override
	public void mouseEntered(MouseEvent e)
	{
		
	}
	
	@Override
	public void mouseExited(MouseEvent e)
	{
		
	}
	
	@Override
	public void mousePressed(MouseEvent ev)
	{
		if(ev.getButton() == MouseEvent.BUTTON1 || ev.getButton() == MouseEvent.BUTTON3)
		{
			assert ev.getSource() == rootPanel;
			ActionNodePanel<?> comp = getANPAt(ev.getPoint());
			if(comp == null)
				return;
			
			copy = ev.getButton() == MouseEvent.BUTTON3;
			dragging = (ActionNodePanel<?>) comp;
			originalIndex = dragging.node.parentField.indexOf(dragging.node);
			
			try
			{
				Point absolute = dragging.getAbsoluteLocation();
				
				offset.x = (ActionNodePanel.MAJOR_ICON_DIM+ActionNodePanel.MINOR_ICON_DIM)/2;//e.getX();
				offset.y = (ActionNodePanel.MAJOR_ICON_DIM+ActionNodePanel.MINOR_ICON_DIM)/2;//e.getY();
				start.x = absolute.x;
				start.y = absolute.y;
				
				int w = dragging.getWidth(), h = dragging.getHeight();
				
				buffer = new BufferedImage(w, h, BufferedImage.TYPE_4BYTE_ABGR);
				Graphics2D canvas = buffer.createGraphics();
				synchronized(ActionNodePanel.class)
				{
					dragging.renderPreview = true;
					dragging.paint(canvas);
					dragging.renderPreview = false;
				}
				canvas.dispose();
				
				if(!copy)
					dragging.node.parentField.remove(dragging.node, this, true);
				
				
				dragPosition.setLocation(absolute);
				dragPosition.setSize(w, h);
				rootPanel.repaint();

				rootPanel.setFocusable(true);
				rootPanel.requestFocusInWindow();
				
				state = DragState.DRAGGING;
			}
			catch (IllegalArgumentException | PropertyVetoException e)
			{
				if(buffer != null)
				{
					buffer.flush();
					buffer = null;
				}
				rootPanel.userError(dragging, e);
				dragging = null;
			}
		}
	}
	
	private ActionNodePanel<?> getANPAt(Point point)
	{
		ActionNodePanel<?> r = null;
		Component current = rootPanel;
		while(true)
		{
			if(current instanceof ActionNodePanel)
				r = (ActionNodePanel<?>) current;
			Component next = current.getComponentAt(point);
			point.translate(-next.getX(), -next.getY());
			if(next == current || next == null)
				break;
			current = next;
		}
		
		return r;
	}

	protected void paintComponent(Graphics g)
	{
		if(dragging == null)
			return;
		g.translate(dragPosition.x, dragPosition.y);
		
		((Graphics2D)g).setComposite(new BlendComposite(state.color));
		g.drawImage(buffer, 0, 0, null);
		((Graphics2D)g).setComposite(AlphaComposite.SrcOver);
		
		g.translate(-dragPosition.x, -dragPosition.y);
	}
	
	@Override
	public void mouseReleased(MouseEvent e)
	{
		clear();
	}
	
	public void clear()
	{
		if(dragging != null)
		{
			if(!copy)
			{
				try
				{
					addNode(dragging.node.parentField, dragging.node, originalIndex);
				}
				catch (IllegalArgumentException | PropertyVetoException e)
				{
					System.err.println("Warning: could not readd dragged component "+dragging);
					e.printStackTrace();
				}
			}
			buffer.flush();
			buffer = null;
			rootPanel.revalidate();
			rootPanel.repaint();
			dragging = null;
			rootPanel.setFocusable(false);
			state = DragState.NO;
		}
	}
	
	private<X extends LapNode> void addNode(ChildField<X> parentField, LapNode node, int index) throws IllegalArgumentException, PropertyVetoException
	{
		parentField.insert(index, parentField.getType().cast(node), this, true);
	}

	public void mouseMove(MouseEvent e)
	{
		if(dragging != null)
		{
			Point mousePoint = e.getPoint();
			int nx = mousePoint.x -offset.x;
			int ny = mousePoint.y -offset.y;
			Point p = new Point(nx, ny);
			
			Socket s = rootPanel.getSocketAt(new Point(mousePoint.x, mousePoint.y));
			if(s != null)
			{
				if(s.accepts(dragging.node))
					state = DragState.SNAP_VALID;
				else
					state = DragState.SNAP_INVALID;
				p = s.getAbsoluteLocation();
			}
			else
				state = DragState.DRAGGING;
			dragPosition.setLocation(p);
			rootPanel.repaint();
		}
	}

	@Override
	public void mouseDragged(MouseEvent e)
	{
		mouseMove(e);
	}

	@Override
	public void mouseMoved(MouseEvent e)
	{
		mouseMove(e);
	}
	
}
